package org.arch_learn.mvc.servlet;

import org.apache.commons.lang3.StringUtils;
import org.arch_learn.ioc_aop.ioc.BeanFactory;
import org.arch_learn.mvc.annos.AutoWired;
import org.arch_learn.mvc.annos.Controller;
import org.arch_learn.mvc.annos.RequestMapping;
import org.arch_learn.mvc.annos.Security;
import org.arch_learn.mvc.pojo.Handler;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


public class GeneralDispatcherServlet extends HttpServlet {
    //加载的配置信息
    private Properties properties = new Properties();

    //缓存到扫描的全限定类名
    private Set<String> classNames = new HashSet<>();

    //mvc单例池
    private HashMap<String, Object> mvcSingletonPool = new HashMap<>();

    //handlers列表
    private List<Handler> handlers = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Handler handler = getHandler(req, resp);
        if (handler == null) return;

        checkAuth(handler, req, resp);
        if (resp.getStatus() == 403) return;

        //参数绑定
        //获取所有参数类型数组，这个数组的长度就是需要传入的args数组的长度
        Parameter[] parameters = handler.getMethod().getParameters();
        //根据形参数组长度创建实参数组，传入反射调用
        Object[] args = new Object[parameters.length];
        //给实参数组塞值，必须保证实参的顺序和形参顺序一致
        Map<String, String[]> parameterMap = req.getParameterMap();
        //遍历得到所有req中的参数
        parameterMap.entrySet().forEach(paramEntry -> {
            String[] values = paramEntry.getValue();
            String valuesStr = StringUtils.join(values, ",");
            //如果参数和方法中的参数匹配上了，填充数据
            if (handler.getParamIndexMapping().containsKey(paramEntry.getKey())) {
                Integer index = handler.getParamIndexMapping().get(paramEntry.getKey());
                args[index] = valuesStr;
            }
        });

        Integer httpServletRequestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        if (httpServletRequestIndex != null) {
            args[httpServletRequestIndex] = req;
        }
        Integer httpServletResponseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        if (httpServletResponseIndex != null) {
            args[httpServletResponseIndex] = resp;
        }
        try {
            handler.getMethod().invoke(handler.getControllerObj(), args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private void checkAuth(Handler handler, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        Method method = handler.getMethod();
        Security annotation = method.getAnnotation(Security.class);
        if (null != annotation) {
            String[] names = annotation.names();
            Map<String, String[]> parameterMap = req.getParameterMap();
            String[] namesValue = parameterMap.get("name");
            String namesStr = StringUtils.join(namesValue, ",");
            if (Arrays.stream(names).noneMatch(v -> v.equalsIgnoreCase(namesStr))) {
                resp.setStatus(403);
                resp.getWriter().write("no auth");
            }
        }
    }

    private Handler getHandler(HttpServletRequest req, HttpServletResponse resp) {
        if (handlers.isEmpty()) return null;
        String requestURI = req.getRequestURI();
        List<Handler> matchesHandlers = handlers.stream().filter(v -> v.getPattern().matcher(requestURI).matches())
                .collect(Collectors.toList());
        if (matchesHandlers.size() > 1) throw new RuntimeException("多个handler匹配url");
        if (matchesHandlers.size() == 0) {
            resp.setStatus(404);
            return null;
        } else {
            return matchesHandlers.get(0);
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件 springmvc.properties
        String mvcContextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(mvcContextConfigLocation);

        //2.扫描相关类与注解
        doScan(properties.getProperty("scanPackage"));

        //3.初始化bean，实现ioc子容器
        doInstance();

        //4.维护对象依赖关系实现依赖注入
        doAutoWired();

        //5.初始化mvc组件，构造handlerMapping处理器映射器，将配置好的url和method建立映射关系
        initHandlerMappings();

        //等待请求j进入，处理请求
        System.out.println("自定义mvc初始化完成...");
    }

    private void initHandlerMappings() {
        if (mvcSingletonPool.isEmpty()) return;
        mvcSingletonPool.entrySet().forEach(entry -> {
            Class<?> clazz = entry.getValue().getClass();
            if (clazz.isAnnotationPresent(Controller.class)) {
                String baseUrl = "";
                if (clazz.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                    baseUrl = requestMapping.value();
                }
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                        String methodUrl = annotation.value();
                        String url = baseUrl + methodUrl;
                        Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url));
                        Parameter[] parameters = method.getParameters();
                        for (int i = 0; i < parameters.length; i++) {
                            Parameter parameter = parameters[i];
                            //参数中的HttpServletRequest和HttpServletResponse做特殊处理
                            if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                                handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
                            } else {
                                handler.getParamIndexMapping().put(parameter.getName(), i);
                            }
                        }
                        handlers.add(handler);
                    }
                }
            }
        });
    }

    private void doAutoWired() {
        if (this.mvcSingletonPool.size() == 0) return;
        mvcSingletonPool.entrySet().forEach(entry -> {
            Object obj = entry.getValue();
            Field[] fields = obj.getClass().getDeclaredFields();
            Arrays.stream(fields).forEach(f -> {
                if (f.isAnnotationPresent(AutoWired.class)) {
                    AutoWired annotation = f.getAnnotation(AutoWired.class);
                    String value = annotation.value();
                    Class<?> clazz = f.getType();
                    String name = clazz.getName();
                    String key = value.length() == 0 ? name : value;
                    Object o = mvcSingletonPool.get(key) == null ? BeanFactory.getBean(key) : mvcSingletonPool.get(key);
                    if (o != null) {
                        f.setAccessible(true);
                        try {
                            f.set(obj, o);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }

                }
            });
        });
    }

    //基于classNames缓存的全限定类名,以及反射技术，完成对象的创建和管理
    private void doInstance() {
        if (classNames.size() == 0) return;
        classNames.forEach(v -> {
            try {
                Class<?> clazz = Class.forName(v);
                if (clazz.isAnnotationPresent(Controller.class)) {
                    Controller annotation = clazz.getAnnotation(Controller.class);
                    String key = annotation.value().length() == 0 ? clazz.getName() : annotation.value();
                    mvcSingletonPool.put(key, clazz.newInstance());
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });
    }

    private void doScan(String scanPackage) {
        String scanPath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File root = new File(scanPath);
        File[] files = root.listFiles();
        for (File f : files) {
            if (f.isDirectory()) {
                doScan(scanPackage + "." + f.getName());
            } else if (f.getName().endsWith(".class")) {
                String className = scanPackage + "." + f.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

    private void doLoadConfig(String mvcContextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(mvcContextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
